#include "shm_ipc.h"
#include <limits.h>
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include "game_event.h"
#include "oper_config.h"

//#define USE_ATOMIC

static shm_ipc_obj *init_shm_ipc_obj(int key, int size, bool create)
{
	int flag = 0666;
	if (create)
	{
		flag |= IPC_CREAT|IPC_EXCL;
	}
	int shmid = shmget(key, size, flag);
	if (shmid == -1) {
		LOG_ERR("%s %d: shmget %x fail[%d] size[%d] flag[%d]", __FUNCTION__, __LINE__, key, errno, size, flag);
		return NULL;
	}
	
	shm_ipc_obj *ret;
	ret = (shm_ipc_obj *)shmat(shmid, NULL, 0);
	if (ret == (void *)-1) {
		LOG_ERR("%s %d: shmat %x fail[%d] size[%d] flag[%d]", __FUNCTION__, __LINE__, key, errno, size, flag);
		return NULL;
	}

	if (create)
	{
		ret->size = size;
		ret->read = ret->write = 0;
		LOG_DEBUG("%s: ret = %p, read = %p, write = %p, ke = %d, size = %d", __FUNCTION__, ret, &ret->read, &ret->write, key, size);
//		ret->mem = (void *)(ret->data);
//		ret->read_ptr = ret->write_ptr = ret->mem;
//		ret->end = ret->mem + size;
	}
	
	return (ret);
}

shm_ipc_obj *init_shm_from_config(const char *prefix, FILE *file, bool create)
{
	char *line;
	int addr;
	int size;
	char t[128];

	sprintf(t, "%s_addr", prefix);
	line = get_first_key(file, t);
	if (!line) {
		LOG_ERR("config file wrong, no %s", t);
		return NULL;		
	}
	addr = strtol(get_value(line), NULL, 0);
	if (addr <= 0) {
		LOG_ERR("config file wrong, no %s", t);
		return NULL;
	}
    sprintf(t, "%s_size", prefix);									  
    line = get_first_key(file, t);
	if (!line) {
		LOG_ERR("config file wrong, no %s", t);
		return NULL;		
	}
	size = strtol(get_value(line), NULL, 0);
	if (size <= 0) {
		LOG_ERR("config file wrong, no %s", t);
		return NULL;
	}

	return init_shm_ipc_obj(addr, size, create);
}

void rm_shm_ipc_obj(int shmid)
{
	shmctl(shmid, IPC_RMID, NULL);	
}

PROTO_HEAD *read_from_shm_ipc(shm_ipc_obj *obj)
{
//	if (obj->read == obj->write)
#ifdef USE_ATOMIC
	if (obj->read == __atomic_load_n(&obj->write, __ATOMIC_SEQ_CST))
#else		
 	if (obj->read == obj->write)
#endif		
		return NULL;
	
	PROTO_HEAD *head = READ_DATA(obj);
	obj->read += head->len;

	LOG_DEBUG("%s: cmd = %d, size = %d [%u %u %u]", __FUNCTION__, head->msg_id, head->len, obj->read, obj->write, obj->size);	
	return head;
}

void try_read_reset(shm_ipc_obj *obj)
{
	uint32_t t = obj->read;
	if (__atomic_compare_exchange_n(&obj->write, &t, 0, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
	{
		obj->read = 0;
		LOG_DEBUG("%s: %u %u %u", __FUNCTION__, obj->read, obj->write, obj->size);
	}
}

static void shm_ipc_move(shm_ipc_obj *obj, PROTO_HEAD *head)
{
//	assert(obj->read == 0);
	uint32_t len = head->len;
	memmove(obj->data, head, len);
#ifdef USE_ATOMIC	
	__atomic_store_n(&obj->write, len, __ATOMIC_SEQ_CST);
#else	
	obj->write = len;
#endif	

	LOG_DEBUG("%s: cmd = %d, size = %d [%u %u %u]", __FUNCTION__, head->msg_id, head->len, obj->read, obj->write, obj->size);		
}

int shm_ipc_obj_avaliable_size(shm_ipc_obj *obj)
{
#ifdef USE_ATOMIC
	return obj->size - __atomic_load_n(&obj->write, __ATOMIC_SEQ_CST) - SHM_DATA_OFFSET;
#else	
 	return obj->size - obj->write - SHM_DATA_OFFSET;
#endif	
}

static void shm_ipc_obj_write_impl(shm_ipc_obj *obj, PROTO_HEAD *head)
{
		//如果被重置了，那么要做memmove
		//如果没有重置，随时可能被重置
//	void *addr1 = (void *)head + head->len;  //未重置地址
//	void *addr2 = obj->mem + head->len;   //重置地址
#ifdef USE_ATOMIC
	uint32_t addr1 = __atomic_load_n(&obj->write, __ATOMIC_SEQ_CST) + head->len;
#else	
	uint32_t addr1 = obj->write + head->len;
#endif
//	uint32_t addr2 = head->len;
	for (;;)
	{
//		void *t1 = head;
//		void *t2 = obj->mem;
		uint32_t t1 = (char *)head - (char *)obj - SHM_DATA_OFFSET;
//		uint32_t t2 = 0;
		if (__atomic_compare_exchange_n(&obj->write, &t1, addr1, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
		{
			return;
		}
//		else if (__atomic_compare_exchange_n(&obj->write, &t2, addr2, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))
		else
		{
#ifdef USE_ATOMIC			
			assert(__atomic_load_n(&obj->write, __ATOMIC_SEQ_CST) == 0);
#else			
 			assert(obj->write == 0);
#endif			
//			assert(obj->read == 0);
			shm_ipc_move(obj, head);
			return;
		}
//		else
//		{
//			LOG_ERR("%s: %d  it should be a bug\n", __FUNCTION__, __LINE__);
//		}
			
	}
	return;
}

static shm_ipc_write_failed write_failed_callback;
void set_shm_ipc_write_failed(shm_ipc_write_failed callback)
{
	write_failed_callback = callback;
}

int write_to_shm_ipc_start(shm_ipc_obj *obj, int len)
{
	if (shm_ipc_obj_avaliable_size(obj) < len)
	{
		if (write_failed_callback)
			return write_failed_callback(obj, NULL, len);
		return -10;
	}
	assert(len >= int(sizeof(PROTO_HEAD)));	
	return (0);	
}
int write_to_shm_ipc_end(shm_ipc_obj *obj, PROTO_HEAD *head)
{
//	assert(shm_ipc_obj_avaliable_size(obj) >= len);
//	PROTO_HEAD *head = WRITE_DATA(obj);
//	assert((int)head->len == len);
	assert(head && head->len >= sizeof(PROTO_HEAD));
	
	shm_ipc_obj_write_impl(obj, head);
	LOG_DEBUG("%s: %u %u %u: %d %d", __FUNCTION__, obj->read, obj->write, obj->size, head->msg_id, head->len);
	return (0);
}

int pack_to_shm_ipc(shm_ipc_obj *obj, void *data, PACK_FUNC pack_func, PACK_SIZE_FUNC pack_size_func, uint16_t msg_id, uint64_t player_id)
{
	PROTO_HEAD *shm_head = WRITE_DATA(obj);
	ssize_t size = pack_size_func(data) + sizeof(PROTO_HEAD);
	if (size < 0 || size >= shm_ipc_obj_avaliable_size(obj))
	{
		LOG_ERR("%s: pack msg[%d] failed, size = %lu", __FUNCTION__, msg_id, size);
		return -1;
	}
	
	size = pack_func(data, shm_head->data);
	if (size < 0)
		return -1;
	shm_head->len = size + sizeof(PROTO_HEAD);
	shm_head->msg_id = msg_id;
	shm_head->player_id = player_id;
	shm_ipc_obj_write_impl(obj, shm_head);

	LOG_DEBUG("%s: cmd = %d, size = %lu [%u %u %u]", __FUNCTION__, msg_id, size, obj->read, obj->write, obj->size);
	return (0);
}

int write_to_shm_ipc(shm_ipc_obj *obj, char *buf, int len, uint16_t msg_id, uint64_t player_id)
{
	if (shm_ipc_obj_avaliable_size(obj) < (int)(len + sizeof(PROTO_HEAD)))
	{
//		if (write_failed_callback)
//			return write_failed_callback(obj, buf, len);
		return -10;
	}

	PROTO_HEAD *shm_head = WRITE_DATA(obj);
	shm_head->len = len + sizeof(PROTO_HEAD);
	shm_head->msg_id = msg_id;
	shm_head->player_id = player_id;
	memcpy(shm_head->data, buf, len);

	shm_ipc_obj_write_impl(obj, shm_head);

	LOG_DEBUG("%s: cmd = %d, size = %d [%u %u %u]", __FUNCTION__, msg_id, len, obj->read, obj->write, obj->size);	
	return (0);
}

// int write_to_shm_ipc(shm_ipc_obj *obj, PROTO_HEAD *head)
// {
// 	if (shm_ipc_obj_avaliable_size(obj) < (int)head->len)
// 	{
// 		if (write_failed_callback)
// 			return write_failed_callback(obj, head, head->len);
// 		return -10;
// 	}

// 	PROTO_HEAD *shm_head = WRITE_DATA(obj);
// 	memcpy(shm_head, head, head->len);

// 	shm_ipc_obj_write_impl(obj, shm_head);
// 	return (0);
// }

